#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 时间     : 2020/11/30 09:14
# 作者     : DL (584054432@qq.com)
# 网址     : https://gitee.com/dingliangPython/
# 软件版本 : Python3.7.3
# 功能     ：
# cython: language_level=3
import logging
import os
import time_ext_
from ctypes import byref, c_char, c_char_p, c_double, c_int, c_longlong, c_void_p, cast, CDLL, CFUNCTYPE, POINTER, \
    Structure
from ctypes import windll
from struct import pack, unpack
import win32con
from queue import Queue

# 装饰器
def method(prototype):
    class MethodDescriptor(object):

        def __init__(self, func):
            self.func = func
            self.bound_funcs = {}  # hold on to references to prevent gc

        def __get__(self, obj, sType = None):
            assert obj is not None  # always require an instance
            try:
                return self.bound_funcs[obj, sType]
            except KeyError:
                ret = self.bound_funcs[obj, sType] = prototype(
                        self.func.__get__(obj, sType))
                return ret

    return MethodDescriptor


# 遥信结构体


class GCL_YX_T(Structure):
    _fields_ = [
            ('address', c_int),
            ('value', c_int),
            ('quality', c_int),
            ('datetime', c_longlong)
            ]


# 遥测结构体


class GCL_YC_T(Structure):
    _fields_ = [
            ('address', c_int),
            ('value', c_double),
            ('quality', c_int),
            ('datetime', c_longlong)
            ]


# 遥文结构体


class GCL_YW_T(Structure):
    _fields_ = [
            ('address', c_int),
            ('value', c_char * 128),
            ('quality', c_int),
            ('datetime', c_longlong)
            ]


# 结构体

class GCL_PSM_ATTACH(Structure):
    _fields_ = [
            ('reason', c_int),
            ('containerId', c_int),
            ('sourceId', c_int),
            ('targetId', c_int),
            ('tag', c_int)
            ]


CALLBACK_SDK_ERR = method(CFUNCTYPE(None, c_int))
CALLBACK_SDK_MSG = method(
        CFUNCTYPE(None, c_char_p, c_char_p, c_int, POINTER(GCL_PSM_ATTACH)))
CALLBACK_SDK_FILE = method(CFUNCTYPE(None, c_char_p, POINTER(GCL_PSM_ATTACH)))
CALLBACK_SDK_RT_REQ = method(CFUNCTYPE(None, POINTER(GCL_PSM_ATTACH)))
CALLBACK_SDK_RT_REV = method(
        CFUNCTYPE(None, c_int, c_void_p, c_int, c_int, POINTER(GCL_PSM_ATTACH)))
# 测点类型
MEASURE_TYPE = {1: 0x01010203, 2: 0x0101021C, 3: 0x0101022F}
# 测点数据长度
MEASURE_LENGHT = {1: 20, 2: 24, 3: 144}
MEASURE_FMT = {1: '=iiiq', 2: '=idiq', 3: 'i128siq'}
MEASURE_STRUCT = {1: GCL_YX_T(), 2: GCL_YC_T(), 3: GCL_YW_T()}

cdef class GCL_SDK:
    realTimeDatas = {}
    rt_data = realTimeDatas
    realTimeDataMessage = Queue()
    rt_msg = realTimeDataMessage
    cdef str config_path
    cdef object log, msg_callback, file_callback, rt_callback, lib

    def __cinit__(self, object msg_callback = None, object file_callback = None, object rt_callback = None,
                  str config_path = './'):
        self.log = logging.getLogger("main.sdk")
        # self.realTimeDatas={}
        self.config_path = config_path
        self.msg_callback = msg_callback
        self.file_callback = file_callback
        self.rt_callback = rt_callback
        self.lib = self.init_lib()
        self.init_callbacks()
        self.gcl_sdk_init()

    def get_rt_value(self, mid):

        if mid >= 0x01000000 and mid < 0x02000000:
            index = 1
        elif mid >= 0x02000000 and mid < 0x03000000:
            index = 2
        elif mid >= 0x03000000 and mid < 0x04000000:
            index = 3
        _type = MEASURE_TYPE[index]
        lenght = MEASURE_LENGHT[index]
        fmt = MEASURE_FMT[index]
        _struct = MEASURE_STRUCT[index]()

        buff = pack(fmt, _struct.address, _struct.value, _struct.quality, _struct.datetime)
        result = self.lib.gci_realtime_data_get(mid, _type, buff, lenght, None)
        if not result: return 0

        values = list(unpack(fmt, buff))
        values[3] = values[3] / 1000 if len(str(values[3])) > 10 else values[3]
        return values

    def get_rt_callback(self):
        return self.rt_callback
    def get_msg_callback(self):
        return self.msg_callback
    def get_file_callback(self):
        return self.file_callback

    def set_msg_callback(self, func):
        # if self.msg_callback and func.__name__ == self.msg_callback.__name__:
        #     return
        # self.log.warning("设置消息回调处理函数:{}".format(func.__name__))
        self.msg_callback = func

    def set_rt_callback(self, func):
        # if self.rt_callback and func.__name__ == self.rt_callback.__name__:
        #     return
        # self.log.warning("设置实时数据回调处理函数:{}".format(func.__name__))
        self.rt_callback = func

    def set_file_callback(self, func):
        # if self.file_callback and func.__name__ == self.file_callback.__name__:
        #     return
        # self.log.warning("设置文件回调处理函数:{}".format(func.__name__))
        self.file_callback = func

    # 初始DLL
    cdef init_lib(self):
        cdef str sdkPath = './gcl_sdk1.dll'
        if not os.path.exists(sdkPath):
            msg = "文件夹：{}，缺少gcl_sdk1.dll文件,不能初始化SDK,即将退出".format(os.path.abspath('./'))
            windll.user32.MessageBoxA(0, msg.encode('gbk'), "警告".encode('gbk'), 1 | win32con.MB_ICONWARNING)
            os._exit(0)
        sdkConfigPath = os.path.join(self.config_path, 'gcl_sdk/config/apps.config.ini')
        if not os.path.exists(sdkConfigPath):
            msg = "sdk配置文件:{},没有找到,无法初始化SDK,请检测配置文件....".format(sdkConfigPath)
            windll.user32.MessageBoxA(0, msg.encode('gbk'), "警告".encode('gbk'), 1 | win32con.MB_ICONWARNING)
            os._exit(0)
        return CDLL(os.path.abspath(sdkPath))

    # 初始化SDK
    cdef gcl_sdk_init(self):
        self.init()
        if self.gcl_init_status():
            self.log.info("SDK,初始化成功....")
        if self.gcl_terminal_status():
            self.log.info("SDK,连接总线成功...")

    # 初始化状态
    def gcl_init_status(self):
        if self.lib.gci_init_status() > 0:
            return True
        else:
            return False

    # 连接终端状态
    def gcl_terminal_status(self):
        if self.lib.gci_terminal_status() > 0:
            return True
        else:
            return False

    cdef reg_err_notify(self, fn):
        return self.lib.gci_register_error_notify(fn)

    cdef unreg_err_notify(self, fn):
        return self.lib.gci_unregister_error_notify(fn)

    cdef reg_msg_cmd_notify(self, fn):
        return self.lib.gci_register_message_command_notify(fn)

    cdef unreg_msg_cmd_notify(self, fn):
        return self.lib.gci_unregister_message_command_notify(fn)

    # 注册文件回调
    cdef reg_file_transfer_notify(self, fn):
        return self.lib.gci_register_file_transfer_notify(fn)

    cdef unreg_file_transfer_notify(self, fn):
        return self.lib.gci_unregister_file_transfer_notify(fn)

    cdef reg_file_transfer_result_notify(self, fn):
        return self.lib.gci_register_file_transfer_result_notify(fn)

    cdef unreg_file_transfer_result_notify(self, fn):
        return self.lib.gci_unregister_file_transfer_result_notify(fn)

    cdef reg_rt_rev_notify(self, fn):
        return self.lib.gci_register_realtime_data_post_notify(fn)

    cdef unreg_rt_rev_notify(self, fn):
        return self.lib.gci_unregister_realtime_data_post_notify(fn)

    # 初始化SDK
    cdef init(self):
        configPathPointer = c_char_p()
        cdef bytes configPathDir = (self.config_path + '\\').encode('gbk')
        configPathPointer.value = configPathDir
        return self.lib.gci_init(1, byref(configPathPointer))

    # 回调注册

    cdef init_callbacks(self):
        # 注册消息回调
        self.reg_msg_cmd_notify(self.cb_sdk_msg)
        # 注册实时回调
        self.reg_rt_rev_notify(self.cb_sdk_rt_rev)
        # 注册文件回调
        self.reg_file_transfer_notify(self.cb_sdk_Filr)

    # 卸载SDK
    def uninit(self):
        return self.lib.gci_cleanup()

    # 发送消息
    def set_message(self, str message = "", int cmd=1, int targetId=0):
        cdef dict command = {1: 'send.yk.1', 2: 'send.yt.1', 3: 'send.ys.1'}
        cdef int lenght = len(message)
        cdef bytes send_command = command[cmd].encode('gbk')
        cdef bytes send_msg = message.encode('gbk')
        attach = GCL_PSM_ATTACH()
        attach.targetId = targetId
        cdef int res = self.lib.gci_message_command_send(send_command, send_msg, lenght, attach)
        self.log.warning('发送rt消息:{},{}'.format(command[cmd], message))
        return True if res >= 0 else None
    def get_message(self):
        try:
            if self.realTimeDataMessage.qsize() >= 1:
                command, message = self.realTimeDataMessage.get()
                self.log.info("接收到控制，命令：{}，信息：{}".format(command, message))
                if "send.yk.1" == command or 'yk' in command:
                    try:
                        vDict = dict([s.split("=") for s in message.split(";") if len(s) > 1])
                        return vDict["measure"], vDict["value"]
                    except Exception as e:
                        self.log.warning("解析控制命令失败：{},消息：{}，异常：{}".format(command, message, e))
                        return False, False
                else:
                    self.log.info("接收的是一个消息,命令：{},消息：{}".format(command, message))
                    return command, message
        except Exception as e:
            self.log.warning("获取实时命令出错:{}".format(e))
    # 发送RT数据
    def set_measure(self, int measureId, object value, int quality=1):
        cdef str index = str(measureId)[0]
        cdef object obj = None
        cdef object sender = None
        cdef object send_value
        # 遥信
        try:
            if index == '1':
                obj = GCL_YX_T()
                send_value = int(eval(value))
                sender = self.send_yx
            elif index == '3':
                obj = GCL_YC_T()
                send_value = eval(value)
                sender = self.send_yc
            elif index == '5':
                obj = GCL_YW_T()
                send_value = value.encode('utf-8')
                sender = self.send_yw
            obj.address = measureId
            obj.value = send_value
            obj.quality = quality
            obj.datetime = <long long> (time_ext_.time() * 1000)
            for i in range(5):
                receive = sender(obj)
                if receive > -1:
                    return True
        except Exception as e:
            self.log.warning("测点:{},值:{},不是一个有效的类型,遥信:整形,遥测:浮点或整型,遥文:任意64个字符内".format(measureId, value))
    # 获取RT数据
    def get_measure(self, measureId):
        return self.realTimeDatas.get(measureId)
    # 处理数据
    @staticmethod
    def convert_data(object value, str index):
        if index == '1':
            return <int> value
        if index == '3':
            return <float> value
        if index == '5':
            return value.encode('utf-8')

    # 发送多个RT数据
    def set_measure_list(self, data):
        cdef dict structure_obj = {'1': GCL_YX_T, '3': GCL_YC_T, '5': GCL_YW_T}
        cdef dict sender_obj = {0: self.send_yx_array, 1: self.send_yc_array, 2: self.send_yw_array}
        cdef int size = len(data)
        cdef list yx_list = []
        cdef list yc_list = []
        cdef list yw_list = []
        cdef list rec = []
        cdef int i
        for i in range(size):
            row = data[i]
            index = str(row[0])[0]
            structure = structure_obj[index]()
            structure.address = row[0]
            structure.value = self.convert_data(row[1], index)
            structure.quality = 1
            structure.datetime = <long long> (time_ext_.time() * 1000)
            if index == '1':
                yx_list.append(structure)
            elif index == '3':
                yc_list.append(structure)
            elif index == '5':
                yw_list.append(structure)
        for i, data in enumerate([yx_list, yc_list, yw_list]):
            if data:
                rec.append(sender_obj[i](data))
            else:
                rec.append(0)
        return rec

    def send_yx(self, e):
        cdef bytes buff = pack('=iiiq', e.address, e.value, e.quality, e.datetime)
        return self.lib.gci_realtime_data_post(0x01010203, buff, 20, 1, None)

    def send_yc(self, e):
        cdef bytes buff = pack('=idiq', e.address, e.value, e.quality, e.datetime)
        return self.lib.gci_realtime_data_post(0x0101021C, buff, 24, 1, None)

    def send_yw(self, e):
        cdef bytes buff = pack('=i128siq', e.address, e.value, e.quality, e.datetime)
        return self.lib.gci_realtime_data_post(0x0101022F, buff, 144, 1, None)

    def send_yx_array(self, v):
        cdef bytes buff = b''
        if len(v) > 0:
            for e in v:
                buff += (pack('=iiiq', e.address, e.value, e.quality, e.datetime))
            receive = self.lib.gci_realtime_data_set_list(0x01010203, buff, 20 * len(v), len(v), None)
            return len(v) if receive > 0 else receive

    def send_yc_array(self, v):
        cdef bytes buff = b''
        if len(v) > 0:
            for e in v:
                buff += (pack('=idiq', e.address, e.value, e.quality, e.datetime))
            receive = self.lib.gci_realtime_data_set_list(0x0101021C, buff, 24 * len(v), len(v), None)
            return len(v) if receive > 0 else receive

    def send_yw_array(self, v):
        cdef bytes buff = b''
        cdef int receive = 0
        if len(v) > 0:
            for e in v:
                receive += 1 if self.send_yw(e) > 0 else 0
            return receive

    @CALLBACK_SDK_ERR
    def cb_sdk_error(self, err):
        self.log.error("异常：{}".format(err))

    # 消息回调处理函数
    @CALLBACK_SDK_MSG
    def cb_sdk_msg(self, bytes cmd, bytes pram, int length, attach):
        cdef int sourceId = attach[0].sourceId
        cdef int targetId = attach[0].targetId
        cdef str command = bytes.decode(cmd)
        cdef str message = bytes.decode(pram)

        self.realTimeDataMessage.put((command, message))
        length = length
        self.log.debug("接收到终端源=0x{:0>8X},目标=0x{:0>8X},命令=\"{}\",消息=\"{}\"".format(sourceId, targetId, command, message))
        if self.msg_callback:
            self.msg_callback(command, message, length, sourceId, targetId)

    # 主动发送文件
    def send_file(self, str filePath):
        filePath = filePath.encode('utf-8')
        receive = self.lib.gci_file_transfer_send(filePath, 0)
        if receive > 0:
            self.log.info("发送文件:{},到终端:{},成功".format(filePath, '总线'))
            return True
        info = {
                -1: '链路打开不成功；',
                -2: '没有缓存了',
                -3: "文件不存在",
                -4: "加载不成功",
                0: "文件太大或其它异常",
                }
        self.log.warning(info.get(receive))

    # 文件回调处理
    @CALLBACK_SDK_FILE
    def cb_sdk_Filr(self, file, attach):
        sourceId = attach[0].sourceId
        targetId = attach[0].targetId
        filePath = file.decode('utf-8')
        # 存储到本地
        with open(filePath, 'rb') as f:
            file = f.read()
            fileDir, fileName = os.path.split(filePath)
            file_dir_path = os.path.join(self.config_path, 'temp')
            if not os.path.exists(file_dir_path):
                os.mkdir(file_dir_path)
            path = os.path.join(file_dir_path, fileName)
            with open(path, 'wb') as w:
                w.write(file)
                self.log.warning("文件:{},写入成功".format(path))
        if self.file_callback:
            self.file_callback(filePath, sourceId, targetId)

    @CALLBACK_SDK_RT_REQ
    def cb_sdk_rt_req(self, attach):
        pass

    # 实时数据接收回调
    @CALLBACK_SDK_RT_REV
    def cb_sdk_rt_rev(self, rt_rtype, rt_data, size, count, attach):
        id_list = [attach[0].sourceId, attach[0].targetId]
        x = cast(rt_data, POINTER(c_char * size))
        # 报文
        x = bytes([v[0] for v in x.contents])
        # 长度
        step = int(size / count)
        if rt_rtype == 0x01010203 and count > 0:
            for i in range(0, size, step):
                value = unpack('=iiiq', x[i: i + step])
                if value[1] == -1:
                    return
                value = list(value)
                value[3] = value[3] / 1000
                value += id_list
                self.realTimeDatas.update({value[0]: value})
                if self.rt_callback:
                    self.rt_callback(value)

        elif rt_rtype == 0x0101021C and count > 0:
            for i in range(0, size, step):
                value = unpack('=idiq', x[i: i + step])
                if value[1] == -1.0:
                    return
                value = list(value)
                value[3] = value[3] / 1000
                value += id_list
                self.realTimeDatas.update({value[0]: value})
                if self.rt_callback:
                    self.rt_callback(value)

        elif rt_rtype == 0x0101022F and count > 0:
            for i in range(0, size, step):
                value = unpack('=i128siq', x[i: i + step])
                value = list(value)
                value[3] = value[3] / 1000
                value += id_list
                try:
                    value[1] = value[1].decode('utf', 'ignore').replace('\x00', '')
                    if not value[1]:
                        return
                except:
                    pass
                self.realTimeDatas.update({value[0]: value})
                if self.rt_callback:
                    self.rt_callback(value)